﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using CC.Yacht.Utilities;

namespace CC.Yacht  
{
    [DataContract]
    public class Dice
    {
        #region Constructor
        public Dice()
        {
            // Empty Contstructor;
        }

        public Dice(Die[] dice)
        {
            SetValues(dice);
        }
        #endregion

        #region Private Fields
        private Die _Die1 = new Die();
        private Die _Die2 = new Die();
        private Die _Die3 = new Die();
        private Die _Die4 = new Die();
        private Die _Die5 = new Die();

        private int _Aces;
        private int _Twos;
        private int _Threes;
        private int _Fours;
        private int _Fives;
        private int _Sixes;

        private int _Total;

        private bool _IsFourOfAKind;
        private bool _IsFullHouse;
        private bool _IsLargeStraight;
        private bool _IsSmallStraight;
        private bool _IsThreeOfAKind;
        private bool _IsYacht;
        #endregion

        #region Public Properties
        public Die this[int index]
        {
            get
            {
                if (index < 0 || index > 4)
                {
                    throw new ArgumentOutOfRangeException("index", index, "Dice[index] only accepts values from 0 to 4.");
                }

                switch (index)
                {
                    case 0:
                        return Die1;
                    case 1:
                        return Die2;
                    case 2:
                        return Die3;
                    case 3:
                        return Die4;
                    case 4:
                        return Die5;
                    default:
                        return null;
                }
            }
        }

        public bool CanRoll
        {
            get { return (RollCount < 3); }
        }

        [DataMember]
        public Die Die1
        {
            get { return _Die1; }
            set { _Die1 = value; }
        }

        [DataMember]
        public Die Die2
        {
            get { return _Die2; }
            set { _Die2 = value; }
        }

        [DataMember]
        public Die Die3
        {
            get { return _Die3; }
            set { _Die3 = value; }
        }

        [DataMember]
        public Die Die4
        {
            get { return _Die4; }
            set { _Die4 = value; }
        }

        [DataMember]
        public Die Die5
        {
            get { return _Die5; }
            set { _Die5 = value; }
        }

        public bool HasRolled
        {
            get { return RollCount > 0; }
        }

        public int Count
        {
            get { return 5; }
        }

        [DataMember]
        public int RollCount { get; set; }
        #endregion

        #region Private Methods
        private void CalculateValues()
        {
            ResetValues();

            Dictionary<DieFace, int> uniqueValues = new Dictionary<DieFace, int>();

            for (int i = 0; i < Count; i++)
            {
                if (this[i].Face == DieFace.One)
                {
                    _Aces += 1;
                }
                if (this[i].Face == DieFace.Two)
                {
                    _Twos += 2;
                }
                if (this[i].Face == DieFace.Three)
                {
                    _Threes += 3;
                }
                if (this[i].Face == DieFace.Four)
                {
                    _Fours += 4;
                }
                if (this[i].Face == DieFace.Five)
                {
                    _Fives += 5;
                }
                if (this[i].Face == DieFace.Six)
                {
                    _Sixes += 6;
                }

                _Total += (int)this[i].Face;

                if (!uniqueValues.ContainsKey(this[i].Face))
                {
                    uniqueValues.Add(this[i].Face, 1);
                }
                else
                {
                    uniqueValues[this[i].Face]++;

                    switch (uniqueValues[this[i].Face])
                    {
                        case 5:
                            {
                                _IsFourOfAKind = true;
                                _IsThreeOfAKind = true;
                                _IsYacht = true;
                                break;
                            }
                        case 4:
                            {
                                _IsFourOfAKind = true;
                                _IsThreeOfAKind = true;
                                break;
                            }
                        case 3:
                            {
                                _IsThreeOfAKind = true;
                                break;
                            }
                    }
                }
            }

            if (uniqueValues.Count == 2)
            {
                foreach (KeyValuePair<DieFace, int> pair in uniqueValues)
                {
                    if (pair.Value == 3)
                    {
                        _IsFullHouse = true;
                    }
                }
            }

            if (CheckLargeStraight(uniqueValues))
            {
                _IsLargeStraight = true;
                _IsSmallStraight = true;
            }
            else if (CheckSmallStraight(uniqueValues))
            {
                _IsSmallStraight = true;
            }
        }

        private static bool CheckLargeStraight(IDictionary<DieFace, int> values)
        {
            bool returnValue = false;

            if (values.ContainsKey(DieFace.One) && values.ContainsKey(DieFace.Two) && values.ContainsKey(DieFace.Three) && values.ContainsKey(DieFace.Four) && values.ContainsKey(DieFace.Five))
            {
                returnValue = true;
            }
            else if (values.ContainsKey(DieFace.Two) && values.ContainsKey(DieFace.Three) && values.ContainsKey(DieFace.Four) && values.ContainsKey(DieFace.Five) && values.ContainsKey(DieFace.Six))
            {
                returnValue = true;
            }

            return returnValue;
        }

        private static bool CheckSmallStraight(IDictionary<DieFace, int> values)
        {
            bool returnValue = false;

            if (values.ContainsKey(DieFace.One) && values.ContainsKey(DieFace.Two) && values.ContainsKey(DieFace.Three) && values.ContainsKey(DieFace.Four))
            {
                returnValue = true;
            }
            else if (values.ContainsKey(DieFace.Two) && values.ContainsKey(DieFace.Three) && values.ContainsKey(DieFace.Four) && values.ContainsKey(DieFace.Five))
            {
                returnValue = true;
            }
            else if (values.ContainsKey(DieFace.Three) && values.ContainsKey(DieFace.Four) && values.ContainsKey(DieFace.Five) && values.ContainsKey(DieFace.Six))
            {
                returnValue = true;
            }

            return returnValue;
        }

        private void ResetValues()
        {
            _Aces = 0;
            _Twos = 0;
            _Threes = 0;
            _Fours = 0;
            _Fives = 0;
            _Sixes = 0;

            _Total = 0;

            _IsFourOfAKind = false;
            _IsFullHouse = false;
            _IsLargeStraight = false;
            _IsSmallStraight = false;
            _IsThreeOfAKind = false;
            _IsYacht = false;
        }

        private void SetValues(Die[] dice)
        {
            if (dice.Length != 5)
            {
                throw new ArgumentException("IDie[] must contain five elements!", "dice");
            }

            _Die1 = dice[0];
            _Die2 = dice[1];
            _Die3 = dice[2];
            _Die4 = dice[3];
            _Die5 = dice[4];

            CalculateValues();
        }
        #endregion

        #region Public Methods
        public int GetScore(ScoreField scoreField)
        {
            int returnValue = 0;

            CalculateValues();

            switch (scoreField)
            {
                case ScoreField.Aces:
                    {
                        returnValue = _Aces;
                        break;
                    }
                case ScoreField.Twos:
                    {
                        returnValue = _Twos;
                        break;
                    }
                case ScoreField.Threes:
                    {
                        returnValue = _Threes;
                        break;
                    }
                case ScoreField.Fours:
                    {
                        returnValue = _Fours;
                        break;
                    }
                case ScoreField.Fives:
                    {
                        returnValue = _Fives;
                        break;
                    }
                case ScoreField.Sixes:
                    {
                        returnValue = _Sixes;
                        break;
                    }
                case ScoreField.Chance:
                    {
                        returnValue = _Total;
                        break;
                    }
                case ScoreField.FourOfAKind:
                    {
                        if (_IsFourOfAKind)
                        {
                            returnValue = _Total;
                        }
                        break;
                    }
                case ScoreField.FullHouse: 
                    {
                        if (_IsFullHouse)
                        {
                            returnValue = Constants.FullHouseValue;
                        }
                        break;
                    }
                case ScoreField.LargeStraight:
                    {
                        if (_IsLargeStraight)
                        {
                            returnValue = Constants.LargeStraightValue;
                        }
                        break;
                    }
                case ScoreField.SmallStraight:
                    {
                        if (_IsSmallStraight)
                        {
                            returnValue = Constants.SmallStraightValue;
                        }
                        break;
                    }
                case ScoreField.ThreeOfAKind:
                    {
                        if (_IsThreeOfAKind)
                        {
                            returnValue = _Total;
                        }
                        break;
                    }
                case ScoreField.Yacht:
                    {
                        if (_IsYacht)
                        {
                            returnValue = Constants.YachtValue;
                        }
                        break;
                    }
            }

            return returnValue;
        }

        public void Reset()
        {
            _Die1.Reset();
            _Die2.Reset();
            _Die3.Reset();
            _Die4.Reset();
            _Die5.Reset();
            
            RollCount = 0;

            ResetValues();
        }

        public void Roll()
        {
            if (CanRoll)
            {
                if (!_Die1.Locked)
                {
                    _Die1.Face = RandomHelper.GetDieFace();
                }

                if (!_Die2.Locked)
                {
                    _Die2.Face = RandomHelper.GetDieFace();
                }

                if (!_Die3.Locked)
                {
                    _Die3.Face = RandomHelper.GetDieFace();
                }

                if (!_Die4.Locked)
                {
                    _Die4.Face = RandomHelper.GetDieFace();
                }

                if (!_Die5.Locked)
                {
                    _Die5.Face = RandomHelper.GetDieFace();
                }

                RollCount++;
            }
        }

        public override string ToString()
        {
            return string.Format("Aces: {0} Twos: {1} Threes: {2} Fours: {3} Fives: {4} Sixes: {5} Total: {6} IsFourOfAKind: {7} IsFullHouse: {8} IsLargeStraight: {9} IsSmallStraight: {10} IsThreeOfAKind: {11} IsYacht: {12}", _Aces, _Twos, _Threes, _Fours, _Fives, _Sixes, _Total, _IsFourOfAKind, _IsFullHouse, _IsLargeStraight, _IsSmallStraight, _IsThreeOfAKind, _IsYacht);
        }
        #endregion
    }
}
